knitr::opts_chunk$set(echo = TRUE)
rm(list=ls())
directory <- getwd()
setwd(directory)

Part 2.1-A

df <- list()
name <- c("tr-1k.csv","tr-5k.csv","tr-20k.csv","tr-75k.csv")
for(i in 1:4){
  df[[i]] <- read.csv(name[i], header=FALSE, fill = TRUE, col.names = c("ID","P1","P2","P3","P4","P5","P6","P7","P8"))
}
df.products <- read.csv("products.csv", header = FALSE,col.names = c("ProductID","Productname"))
dim(df[[2]])
[1] 5000    9
new <- list()
num <- c(1000, 5000, 20000, 75000)
for (i in 1:4){
  new[[i]] <- data.frame("ID"=1:num[i])
}
dim(new[[3]])
[1] 20000     1
for (i in 1:4){
  for (j in 1:ncol(df[[i]])){
    product <- df.products$Productname[match(df[[i]][,j], df.products$ProductID)]
    new[[i]] <- cbind(new[[i]], product)
  }
  new[[i]] <-new[[i]][,-1]
}
dim(new[[1]])
[1] 1000    9
for (i in 1:4){
  new[[i]] <- new[[i]][, -1]
}
name_new <- c("tr-1k-canonical.csv", "tr-5k-canonical.csv",
              "tr-20k-canonical.csv", "tr-75k-canonical.csv")
for (i in 1:4){
  write.table(new[[i]], file=name_new[i], sep =',',row.names = FALSE, col.names = FALSE, na='')
}

Part 2.1-B

library("arules")
library("arulesViz")
trans <- read.transactions("tr-1k-canonical.csv", sep=",",header = FALSE)
summary(trans)
transactions as itemMatrix in sparse format with
 1000 rows (elements/itemsets/transactions) and
 50 columns (items) and a density of 0.07076 

most frequent items:
Gongolais Cookie     Truffle Cake     Tuile Cookie       Berry Tart       Hot Coffee          (Other) 
             108              103              102               95               94             3036 

element (itemset/transaction) length distribution:
sizes
  1   2   3   4   5   6   7   8 
 60 162 338 216 132  44  32  16 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1.00    3.00    3.00    3.54    4.00    8.00 

includes extended item information - examples:
inspect(trans[1:5])
freq.itemset <- apriori(trans, parameter=list(support=0.1, target="frequent itemsets"))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 100 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 1000 transaction(s)] done [0.00s].
sorting and recoding items ... [3 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 done [0.00s].
sorting transactions ... done [0.00s].
writing ... [3 set(s)] done [0.00s].
creating S4 object  ... done [0.00s].
inspect(sort(freq.itemset, decreasing = T, by="count"))
rm(freq.itemset)
itemFrequencyPlot(trans, support = 0.1)

image(trans)

rules <- apriori(trans)
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 100 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 1000 transaction(s)] done [0.00s].
sorting and recoding items ... [3 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 done [0.00s].
writing ... [0 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].
rm(rules)
freq.itset <- apriori(trans, parameter=list(support=0.01, target="frequent itemsets"))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 10 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 1000 transaction(s)] done [0.00s].
sorting and recoding items ... [50 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 5 done [0.00s].
sorting transactions ... done [0.00s].
writing ... [132 set(s)] done [0.00s].
creating S4 object  ... done [0.00s].
inspect(sort(freq.itset, decreasing = T, by="count"))
rm(freq.itset)
itemFrequencyPlot(trans, support = 0.01)

image(trans)

rules <- apriori(trans, parameter = list(support=0.01, conf=0.5))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 10 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 1000 transaction(s)] done [0.00s].
sorting and recoding items ... [50 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 5 done [0.00s].
writing ... [124 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].
summary(rules)
set of 124 rules

rule length distribution (lhs + rhs):sizes
 2  3  4  5 
22 69 28  5 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   2.00    3.00    3.00    3.13    4.00    5.00 

summary of quality measures:
    support         confidence       coverage           lift           count     
 Min.   :0.0180   Min.   :0.500   Min.   :0.0180   Min.   : 5.21   Min.   :18.0  
 1st Qu.:0.0190   1st Qu.:0.679   1st Qu.:0.0240   1st Qu.: 9.55   1st Qu.:19.0  
 Median :0.0275   Median :0.905   Median :0.0300   Median :11.59   Median :27.5  
 Mean   :0.0284   Mean   :0.832   Mean   :0.0378   Mean   :11.17   Mean   :28.4  
 3rd Qu.:0.0320   3rd Qu.:0.974   3rd Qu.:0.0403   3rd Qu.:13.30   3rd Qu.:32.0  
 Max.   :0.0580   Max.   :1.000   Max.   :0.1080   Max.   :19.61   Max.   :58.0  

mining info:
inspect(head(rules, by="confidence"))
rules_support <- sort(rules, by="support")
rules_lift <- sort(rules, by="lift")
inspect(head(rules_support,3))
new.rules <- rules[!duplicated(generatingItemsets(rules))]
rules_support_new <- sort(new.rules, by="support")
rules_lift_new <- sort(new.rules, by="lift")
rules_confi_new <- sort(new.rules, by="confidence")
inspect(head(rules_support_new,3))
inspect(head(rules_confi_new,10))
plot(new.rules, engine="htmlwidget")
To reduce overplotting, jitter is added! Use jitter = 0 to prevent jitter.
cat("The top 3 items selected are those which have highest support level:", "\n")
The top 3 items selected are those which have highest support level: 
cat("{Truffle Cake} => {Gongolais Cookie}", "\n")
{Truffle Cake} => {Gongolais Cookie} 
cat("{Marzipan Cookie} => {Truile Cookie}", "\n")
{Marzipan Cookie} => {Truile Cookie} 
cat("{Strawberry Cake} => {Napoleon Cake}", "\n")
{Strawberry Cake} => {Napoleon Cake} 
cat("The top 3 items selected are those which have highest confidence level:", "\n")
The top 3 items selected are those which have highest confidence level: 
cat("{Apple Danish, Apple Tart} => {Apple Croissant}", "\n")
{Apple Danish, Apple Tart} => {Apple Croissant} 
cat("{Apricot Danish, Opera Cake} => {Cherry Tart}", "\n")
{Apricot Danish, Opera Cake} => {Cherry Tart} 
cat("{Apple Danish, Apple Tart, Cherry Soda} => {Apple Croissant}", "\n")
{Apple Danish, Apple Tart, Cherry Soda} => {Apple Croissant} 
cat("Rule 7 with support of 0.31","\n")
Rule 7 with support of 0.31 
cat("{Apple Danish, Apple Tart, Cherry Soda} => {Apple Croissant}", "\n", "\n")
{Apple Danish, Apple Tart, Cherry Soda} => {Apple Croissant} 
 
cat("Rule 9 with support of 0.40","\n")
Rule 9 with support of 0.40 
cat("{Apple Danish, Apple Tart} => {Apple Croissant}", "\n")
{Apple Danish, Apple Tart} => {Apple Croissant} 
trans.list <- list()
trans.name <- c("tr-1k-canonical.csv", "tr-5k-canonical.csv",
                "tr-20k-canonical.csv", "tr-75k-canonical.csv")
for (i in 1:4){
  trans.list[[i]] <- read.transactions(trans.name[i], sep=",",header = FALSE)
}

1000 dataset

freq_itemset_1k <- apriori(trans.list[[1]], parameter=list(support=0.1, target="frequent itemsets"))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 100 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 1000 transaction(s)] done [0.00s].
sorting and recoding items ... [3 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 done [0.00s].
sorting transactions ... done [0.00s].
writing ... [3 set(s)] done [0.00s].
creating S4 object  ... done [0.00s].
inspect(sort(freq_itemset_1k, decreasing = T, by="count"))
rm(freq_itemset_1k)
itemFrequencyPlot(trans.list[[1]], support = 0.1)

image(trans.list[[1]])

These results are the similar to the one obtained in previous section for the 1000 dataset.

5000 dataset

freq_itemset_5k <- apriori(trans.list[[2]], parameter=list(support=0.1, target="frequent itemsets"))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 500 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 5000 transaction(s)] done [0.00s].
sorting and recoding items ... [2 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 done [0.00s].
sorting transactions ... done [0.00s].
writing ... [2 set(s)] done [0.00s].
creating S4 object  ... done [0.00s].
inspect(sort(freq_itemset_5k, decreasing = T, by="count"))
rm(freq_itemset_5k)
itemFrequencyPlot(trans.list[[2]], support = 0.1)

image(trans.list[[2]])

These results for the 5000 dataset for the same minsup vlue of 0.1 the total number of itemsets is lower than the 1000 dataset.

20000 dataset

freq_itemset_20k <- apriori(trans.list[[3]], parameter=list(support=0.1, target="frequent itemsets"))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 2000 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 20000 transaction(s)] done [0.01s].
sorting and recoding items ... [2 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 done [0.00s].
sorting transactions ... done [0.00s].
writing ... [2 set(s)] done [0.00s].
creating S4 object  ... done [0.00s].
inspect(sort(freq_itemset_20k, decreasing = T, by="count"))
rm(freq_itemset_20k)
itemFrequencyPlot(trans.list[[3]], support = 0.1)

image(trans.list[[3]])

These results for the 20000 dataset is similar to the 5000 dataset.

75000 dataset

freq_itemset_75k <- apriori(trans.list[[4]], parameter=list(support=0.1, target="frequent itemsets"))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 7500 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 75000 transaction(s)] done [0.02s].
sorting and recoding items ... [3 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 done [0.00s].
sorting transactions ... done [0.00s].
writing ... [3 set(s)] done [0.00s].
creating S4 object  ... done [0.01s].
inspect(sort(freq_itemset_75k, decreasing = T, by="count"))
rm(freq_itemset_75k)
itemFrequencyPlot(trans.list[[4]], support = 0.1)

image(trans.list[[4]])

These results for the 75000 dataset is more than the 5000 and the 20000 dataset witht the exception of Tuile Cookie.

Minsup value

There is a change in the frequent items from the 1000 dataset and high quantity of types of Coffee from the 5000 dataset.The number of these frequent items is increased in each dataset. Thus customers continue to buy coffee till the 75000 datatset and the the Tuile Cookie is another frequent item.

  • Gongolais Cookie
  • Truffle Cake
  • Tuile Cookie

To the most frequent items:

-Coffee Eclair -Hot Coffee

Output for each rule.

We try to create new rules using the minsup value to 0.01.

1000 DATASET

rules.1k <- apriori(trans.list[[1]], parameter = list(support=0.01, conf=0.5))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 10 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 1000 transaction(s)] done [0.00s].
sorting and recoding items ... [50 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 5 done [0.00s].
writing ... [124 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].
new.rules.1k <- rules.1k[!duplicated(generatingItemsets(rules.1k))]
summary(new.rules.1k)
set of 47 rules

rule length distribution (lhs + rhs):sizes
 2  3  4  5 
16 23  7  1 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   2.00    2.00    3.00    2.85    3.00    5.00 

summary of quality measures:
    support         confidence       coverage           lift           count     
 Min.   :0.0180   Min.   :0.500   Min.   :0.0180   Min.   : 5.21   Min.   :18.0  
 1st Qu.:0.0210   1st Qu.:0.558   1st Qu.:0.0235   1st Qu.: 6.60   1st Qu.:21.0  
 Median :0.0290   Median :0.889   Median :0.0330   Median :10.07   Median :29.0  
 Mean   :0.0311   Mean   :0.794   Mean   :0.0449   Mean   : 9.48   Mean   :31.1  
 3rd Qu.:0.0400   3rd Qu.:0.962   3rd Qu.:0.0735   3rd Qu.:11.78   3rd Qu.:40.0  
 Max.   :0.0580   Max.   :1.000   Max.   :0.1030   Max.   :13.89   Max.   :58.0  

mining info:
new.rules.1k.sort.support <- sort(new.rules.1k, by="support")
new.rules.1k.sort.lift <- sort(new.rules.1k, by="lift")
new.rules.1k.sort.confi <- sort(new.rules.1k, by="confidence")

5000 DATASET

rules.5k <- apriori(trans.list[[2]], parameter = list(support=0.01, conf=0.5))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 50 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 5000 transaction(s)] done [0.00s].
sorting and recoding items ... [50 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 5 done [0.00s].
writing ... [115 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].
new.rules.5k <- rules.5k[!duplicated(generatingItemsets(rules.5k))]
summary(new.rules.5k)
set of 42 rules

rule length distribution (lhs + rhs):sizes
 2  3  4  5 
11 23  7  1 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   2.00    2.25    3.00    2.95    3.00    5.00 

summary of quality measures:
    support         confidence       coverage           lift           count    
 Min.   :0.0212   Min.   :0.504   Min.   :0.0212   Min.   : 4.57   Min.   :106  
 1st Qu.:0.0217   1st Qu.:0.631   1st Qu.:0.0233   1st Qu.: 6.53   1st Qu.:109  
 Median :0.0276   Median :0.915   Median :0.0303   Median :11.57   Median :138  
 Mean   :0.0308   Mean   :0.828   Mean   :0.0424   Mean   :10.52   Mean   :154  
 3rd Qu.:0.0403   3rd Qu.:0.944   3rd Qu.:0.0688   3rd Qu.:13.90   3rd Qu.:202  
 Max.   :0.0512   Max.   :1.000   Max.   :0.0892   Max.   :15.58   Max.   :256  

mining info:
new.rules.5k.sort.support <- sort(new.rules.5k, by="support")
new.rules.5k.sort.lift <- sort(new.rules.5k, by="lift")
new.rules.5k.sort.confi <- sort(new.rules.5k, by="confidence")

20000 DATASET

rules.20k <- apriori(trans.list[[3]], parameter = list(support=0.01, conf=0.5))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 200 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 20000 transaction(s)] done [0.01s].
sorting and recoding items ... [50 item(s)] done [0.00s].
creating transaction tree ... done [0.01s].
checking subsets of size 1 2 3 4 5 done [0.00s].
writing ... [114 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].
new.rules.20k <- rules.20k[!duplicated(generatingItemsets(rules.20k))]
summary(new.rules.20k)
set of 41 rules

rule length distribution (lhs + rhs):sizes
 2  3  4  5 
10 23  7  1 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   2.00    3.00    3.00    2.98    3.00    5.00 

summary of quality measures:
    support         confidence       coverage           lift           count     
 Min.   :0.0204   Min.   :0.502   Min.   :0.0204   Min.   : 4.57   Min.   : 408  
 1st Qu.:0.0207   1st Qu.:0.779   1st Qu.:0.0223   1st Qu.: 7.52   1st Qu.: 413  
 Median :0.0260   Median :0.919   Median :0.0283   Median :12.71   Median : 520  
 Mean   :0.0297   Mean   :0.834   Mean   :0.0407   Mean   :10.60   Mean   : 593  
 3rd Qu.:0.0372   3rd Qu.:0.950   3rd Qu.:0.0437   3rd Qu.:13.47   3rd Qu.: 745  
 Max.   :0.0525   Max.   :0.998   Max.   :0.0912   Max.   :14.58   Max.   :1051  

mining info:
new.rules.20k.sort.support <- sort(new.rules.20k, by="support")
new.rules.20k.sort.lift <- sort(new.rules.20k, by="lift")
new.rules.20k.sort.confi <- sort(new.rules.20k, by="confidence")

75000 DATASET

rules.75k <- apriori(trans.list[[4]], parameter = list(support=0.01, conf=0.5))
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 750 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[50 item(s), 75000 transaction(s)] done [0.02s].
sorting and recoding items ... [50 item(s)] done [0.00s].
creating transaction tree ... done [0.02s].
checking subsets of size 1 2 3 4 5 done [0.01s].
writing ... [116 rule(s)] done [0.00s].
creating S4 object  ... done [0.01s].
# Removing the duplicated rules
new.rules.75k <- rules.75k[!duplicated(generatingItemsets(rules.75k))]
summary(new.rules.75k)
set of 41 rules

rule length distribution (lhs + rhs):sizes
 2  3  4  5 
10 23  7  1 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   2.00    3.00    3.00    2.98    3.00    5.00 

summary of quality measures:
    support         confidence       coverage           lift           count     
 Min.   :0.0206   Min.   :0.503   Min.   :0.0207   Min.   : 5.63   Min.   :1544  
 1st Qu.:0.0208   1st Qu.:0.755   1st Qu.:0.0231   1st Qu.: 7.35   1st Qu.:1560  
 Median :0.0258   Median :0.907   Median :0.0279   Median :13.14   Median :1932  
 Mean   :0.0297   Mean   :0.831   Mean   :0.0409   Mean   :10.72   Mean   :2230  
 3rd Qu.:0.0378   3rd Qu.:0.937   3rd Qu.:0.0435   3rd Qu.:13.50   3rd Qu.:2835  
 Max.   :0.0531   Max.   :1.000   Max.   :0.0926   Max.   :14.73   Max.   :3982  

mining info:
new.rules.75k.sort.support <- sort(new.rules.75k, by="support")
new.rules.75k.sort.lift <- sort(new.rules.75k, by="lift")
new.rules.75k.sort.confi <- sort(new.rules.75k, by="confidence")

Graph for new rules.

plot(new.rules.1k, engine="htmlwidget")
To reduce overplotting, jitter is added! Use jitter = 0 to prevent jitter.
plot(new.rules.5k, engine="htmlwidget")
To reduce overplotting, jitter is added! Use jitter = 0 to prevent jitter.
plot(new.rules.20k, engine="htmlwidget")
To reduce overplotting, jitter is added! Use jitter = 0 to prevent jitter.
plot(new.rules.75k, engine="htmlwidget")
To reduce overplotting, jitter is added! Use jitter = 0 to prevent jitter.

Part 2.1-c

Results observed are as follows

The total number of rules hardly varies. The total number of rules for a minsup of 0.01 and a confidence of 0.5 is:

CONFIDENCE

In the confidence values the rules are grouped into specific confidence values as the number of transactions increases.

Thus for the 1000 dataset the rules are widely scattered in different confidence values. And when we look at the 75000 dataset, the rules with highest lift value are grouped in confidence values between 0.9 and 1 and the rules with lowest lift value in confidence values between 0.5 and 0.6. This makes sense as more the transactions are inputed in a dataset, the confidence of the condition of the consequent is complied with the antecedent (or not), tends to a specific value.

SUPPORT LEVEL

In the support level, as mentioned above, as the number of transactions increases, the rules are grouped into specific confidence values and then, it is observed that the values with highest lift are grouped in small support values for a higher number of transactions. In case for 1000 dataset, the support values are widely scattered. For 75000 dataset the support values for the highest lift values are grouped between 0.02 and 0.03 and the support values for lowest lift values between 0.04 and 0.05.

Part 2.1-D

new.rules.75K.sort.support <- sort(new.rules.75k, by="support")

(I) Most frequently purchased item or itemset?

The most frequently purchased item or itemset.

inspect(head(new.rules.75K.sort.support,1))
cat("{Apricot Danish} => {Cherry Tart}")
{Apricot Danish} => {Cherry Tart}

Least frequently purchased item or itemset?

The least frequently purchased item or item set.

inspect(tail(new.rules.75k.sort.support,1))
cat("{Apple croissant, Apple Danish, Cherry Soda} => {Apple Tart}")
{Apple croissant, Apple Danish, Cherry Soda} => {Apple Tart}
LS0tCnRpdGxlOiAiSG9tZXdvcmstMDkiCmF1dGhvcjogIlZpamF5IEsuIEd1cmJhbmksIFBoLkQuLCBJbGxpbm9pcyBJbnN0aXR1dGUgb2YgVGVjaG5vbG9neSIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQotLS0KCgpgYGB7cn0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCmBgYHtyfQpybShsaXN0PWxzKCkpCmRpcmVjdG9yeSA8LSBnZXR3ZCgpCnNldHdkKGRpcmVjdG9yeSkKYGBgCgojIyMgUGFydCAyLjEtQQoKYGBge3J9CmRmIDwtIGxpc3QoKQpuYW1lIDwtIGMoInRyLTFrLmNzdiIsInRyLTVrLmNzdiIsInRyLTIway5jc3YiLCJ0ci03NWsuY3N2IikKZm9yKGkgaW4gMTo0KXsKICBkZltbaV1dIDwtIHJlYWQuY3N2KG5hbWVbaV0sIGhlYWRlcj1GQUxTRSwgZmlsbCA9IFRSVUUsIGNvbC5uYW1lcyA9IGMoIklEIiwiUDEiLCJQMiIsIlAzIiwiUDQiLCJQNSIsIlA2IiwiUDciLCJQOCIpKQp9CmRmLnByb2R1Y3RzIDwtIHJlYWQuY3N2KCJwcm9kdWN0cy5jc3YiLCBoZWFkZXIgPSBGQUxTRSxjb2wubmFtZXMgPSBjKCJQcm9kdWN0SUQiLCJQcm9kdWN0bmFtZSIpKQpkaW0oZGZbWzJdXSkKYGBgCgpgYGB7cn0KbmV3IDwtIGxpc3QoKQpudW0gPC0gYygxMDAwLCA1MDAwLCAyMDAwMCwgNzUwMDApCmZvciAoaSBpbiAxOjQpewogIG5ld1tbaV1dIDwtIGRhdGEuZnJhbWUoIklEIj0xOm51bVtpXSkKfQpkaW0obmV3W1szXV0pCmBgYAoKYGBge3J9CmZvciAoaSBpbiAxOjQpewogIGZvciAoaiBpbiAxOm5jb2woZGZbW2ldXSkpewogICAgcHJvZHVjdCA8LSBkZi5wcm9kdWN0cyRQcm9kdWN0bmFtZVttYXRjaChkZltbaV1dWyxqXSwgZGYucHJvZHVjdHMkUHJvZHVjdElEKV0KICAgIG5ld1tbaV1dIDwtIGNiaW5kKG5ld1tbaV1dLCBwcm9kdWN0KQogIH0KICBuZXdbW2ldXSA8LW5ld1tbaV1dWywtMV0KfQpkaW0obmV3W1sxXV0pCmBgYAoKYGBge3J9CmZvciAoaSBpbiAxOjQpewogIG5ld1tbaV1dIDwtIG5ld1tbaV1dWywgLTFdCn0KYGBgCgpgYGB7cn0KbmFtZV9uZXcgPC0gYygidHItMWstY2Fub25pY2FsLmNzdiIsICJ0ci01ay1jYW5vbmljYWwuY3N2IiwKICAgICAgICAgICAgICAidHItMjBrLWNhbm9uaWNhbC5jc3YiLCAidHItNzVrLWNhbm9uaWNhbC5jc3YiKQpmb3IgKGkgaW4gMTo0KXsKICB3cml0ZS50YWJsZShuZXdbW2ldXSwgZmlsZT1uYW1lX25ld1tpXSwgc2VwID0nLCcscm93Lm5hbWVzID0gRkFMU0UsIGNvbC5uYW1lcyA9IEZBTFNFLCBuYT0nJykKfQpgYGAKCiMjIyBQYXJ0IDIuMS1CCgpgYGB7cn0KbGlicmFyeSgiYXJ1bGVzIikKbGlicmFyeSgiYXJ1bGVzVml6IikKYGBgCgpgYGB7cn0KdHJhbnMgPC0gcmVhZC50cmFuc2FjdGlvbnMoInRyLTFrLWNhbm9uaWNhbC5jc3YiLCBzZXA9IiwiLGhlYWRlciA9IEZBTFNFKQpzdW1tYXJ5KHRyYW5zKQpgYGAKCmBgYHtyfQppbnNwZWN0KHRyYW5zWzE6NV0pCmBgYAoKYGBge3J9CmZyZXEuaXRlbXNldCA8LSBhcHJpb3JpKHRyYW5zLCBwYXJhbWV0ZXI9bGlzdChzdXBwb3J0PTAuMSwgdGFyZ2V0PSJmcmVxdWVudCBpdGVtc2V0cyIpKQpgYGAKCmBgYHtyfQppbnNwZWN0KHNvcnQoZnJlcS5pdGVtc2V0LCBkZWNyZWFzaW5nID0gVCwgYnk9ImNvdW50IikpCnJtKGZyZXEuaXRlbXNldCkKYGBgCgpgYGB7cn0KaXRlbUZyZXF1ZW5jeVBsb3QodHJhbnMsIHN1cHBvcnQgPSAwLjEpCmltYWdlKHRyYW5zKQpgYGAKCgoKCgoKYGBge3J9CnJ1bGVzIDwtIGFwcmlvcmkodHJhbnMpCnJtKHJ1bGVzKQpgYGAKCmBgYHtyfQpmcmVxLml0c2V0IDwtIGFwcmlvcmkodHJhbnMsIHBhcmFtZXRlcj1saXN0KHN1cHBvcnQ9MC4wMSwgdGFyZ2V0PSJmcmVxdWVudCBpdGVtc2V0cyIpKQppbnNwZWN0KHNvcnQoZnJlcS5pdHNldCwgZGVjcmVhc2luZyA9IFQsIGJ5PSJjb3VudCIpKQpybShmcmVxLml0c2V0KQppdGVtRnJlcXVlbmN5UGxvdCh0cmFucywgc3VwcG9ydCA9IDAuMDEpCmltYWdlKHRyYW5zKQpgYGAKCgpgYGB7cn0KcnVsZXMgPC0gYXByaW9yaSh0cmFucywgcGFyYW1ldGVyID0gbGlzdChzdXBwb3J0PTAuMDEsIGNvbmY9MC41KSkKc3VtbWFyeShydWxlcykKYGBgCgpgYGB7cn0KaW5zcGVjdChoZWFkKHJ1bGVzLCBieT0iY29uZmlkZW5jZSIpKQpgYGAKCmBgYHtyfQpydWxlc19zdXBwb3J0IDwtIHNvcnQocnVsZXMsIGJ5PSJzdXBwb3J0IikKcnVsZXNfbGlmdCA8LSBzb3J0KHJ1bGVzLCBieT0ibGlmdCIpCmBgYAoKYGBge3J9Cmluc3BlY3QoaGVhZChydWxlc19zdXBwb3J0LDMpKQpgYGAKCmBgYHtyfQpuZXcucnVsZXMgPC0gcnVsZXNbIWR1cGxpY2F0ZWQoZ2VuZXJhdGluZ0l0ZW1zZXRzKHJ1bGVzKSldCnJ1bGVzX3N1cHBvcnRfbmV3IDwtIHNvcnQobmV3LnJ1bGVzLCBieT0ic3VwcG9ydCIpCnJ1bGVzX2xpZnRfbmV3IDwtIHNvcnQobmV3LnJ1bGVzLCBieT0ibGlmdCIpCnJ1bGVzX2NvbmZpX25ldyA8LSBzb3J0KG5ldy5ydWxlcywgYnk9ImNvbmZpZGVuY2UiKQpgYGAKCmBgYHtyfQppbnNwZWN0KGhlYWQocnVsZXNfc3VwcG9ydF9uZXcsMykpCmBgYAoKYGBge3J9Cmluc3BlY3QoaGVhZChydWxlc19jb25maV9uZXcsMTApKQpgYGAKCmBgYHtyfQpwbG90KG5ldy5ydWxlcywgZW5naW5lPSJodG1sd2lkZ2V0IikKYGBgCgpgYGB7cn0KY2F0KCJUaGUgdG9wIDMgaXRlbXMgc2VsZWN0ZWQgYXJlIHRob3NlIHdoaWNoIGhhdmUgaGlnaGVzdCBzdXBwb3J0IGxldmVsOiIsICJcbiIpCmNhdCgie1RydWZmbGUgQ2FrZX0gPT4ge0dvbmdvbGFpcyBDb29raWV9IiwgIlxuIikKY2F0KCJ7TWFyemlwYW4gQ29va2llfSA9PiB7VHJ1aWxlIENvb2tpZX0iLCAiXG4iKQpjYXQoIntTdHJhd2JlcnJ5IENha2V9ID0+IHtOYXBvbGVvbiBDYWtlfSIsICJcbiIpCmBgYAoKCmBgYHtyfQpjYXQoIlRoZSB0b3AgMyBpdGVtcyBzZWxlY3RlZCBhcmUgdGhvc2Ugd2hpY2ggaGF2ZSBoaWdoZXN0IGNvbmZpZGVuY2UgbGV2ZWw6IiwgIlxuIikKY2F0KCJ7QXBwbGUgRGFuaXNoLCBBcHBsZSBUYXJ0fSA9PiB7QXBwbGUgQ3JvaXNzYW50fSIsICJcbiIpCmNhdCgie0Fwcmljb3QgRGFuaXNoLCBPcGVyYSBDYWtlfSA9PiB7Q2hlcnJ5IFRhcnR9IiwgIlxuIikKY2F0KCJ7QXBwbGUgRGFuaXNoLCBBcHBsZSBUYXJ0LCBDaGVycnkgU29kYX0gPT4ge0FwcGxlIENyb2lzc2FudH0iLCAiXG4iKQpgYGAKCmBgYHtyfQpjYXQoIlJ1bGUgNyB3aXRoIHN1cHBvcnQgb2YgMC4zMSIsIlxuIikKY2F0KCJ7QXBwbGUgRGFuaXNoLCBBcHBsZSBUYXJ0LCBDaGVycnkgU29kYX0gPT4ge0FwcGxlIENyb2lzc2FudH0iLCAiXG4iLCAiXG4iKQpjYXQoIlJ1bGUgOSB3aXRoIHN1cHBvcnQgb2YgMC40MCIsIlxuIikKY2F0KCJ7QXBwbGUgRGFuaXNoLCBBcHBsZSBUYXJ0fSA9PiB7QXBwbGUgQ3JvaXNzYW50fSIsICJcbiIpCmBgYAoKCmBgYHtyfQp0cmFucy5saXN0IDwtIGxpc3QoKQp0cmFucy5uYW1lIDwtIGMoInRyLTFrLWNhbm9uaWNhbC5jc3YiLCAidHItNWstY2Fub25pY2FsLmNzdiIsCiAgICAgICAgICAgICAgICAidHItMjBrLWNhbm9uaWNhbC5jc3YiLCAidHItNzVrLWNhbm9uaWNhbC5jc3YiKQpmb3IgKGkgaW4gMTo0KXsKICB0cmFucy5saXN0W1tpXV0gPC0gcmVhZC50cmFuc2FjdGlvbnModHJhbnMubmFtZVtpXSwgc2VwPSIsIixoZWFkZXIgPSBGQUxTRSkKfQpgYGAKCiMjIDEwMDAgZGF0YXNldCAjIwpgYGB7cn0KZnJlcV9pdGVtc2V0XzFrIDwtIGFwcmlvcmkodHJhbnMubGlzdFtbMV1dLCBwYXJhbWV0ZXI9bGlzdChzdXBwb3J0PTAuMSwgdGFyZ2V0PSJmcmVxdWVudCBpdGVtc2V0cyIpKQppbnNwZWN0KHNvcnQoZnJlcV9pdGVtc2V0XzFrLCBkZWNyZWFzaW5nID0gVCwgYnk9ImNvdW50IikpCnJtKGZyZXFfaXRlbXNldF8xaykKaXRlbUZyZXF1ZW5jeVBsb3QodHJhbnMubGlzdFtbMV1dLCBzdXBwb3J0ID0gMC4xKQppbWFnZSh0cmFucy5saXN0W1sxXV0pCmBgYApUaGVzZSByZXN1bHRzIGFyZSB0aGUgc2ltaWxhciB0byB0aGUgb25lIG9idGFpbmVkIGluIHByZXZpb3VzIHNlY3Rpb24gZm9yIHRoZSAxMDAwIGRhdGFzZXQuCgojIyA1MDAwIGRhdGFzZXQgIyMKYGBge3J9CmZyZXFfaXRlbXNldF81ayA8LSBhcHJpb3JpKHRyYW5zLmxpc3RbWzJdXSwgcGFyYW1ldGVyPWxpc3Qoc3VwcG9ydD0wLjEsIHRhcmdldD0iZnJlcXVlbnQgaXRlbXNldHMiKSkKaW5zcGVjdChzb3J0KGZyZXFfaXRlbXNldF81aywgZGVjcmVhc2luZyA9IFQsIGJ5PSJjb3VudCIpKQpybShmcmVxX2l0ZW1zZXRfNWspCml0ZW1GcmVxdWVuY3lQbG90KHRyYW5zLmxpc3RbWzJdXSwgc3VwcG9ydCA9IDAuMSkKaW1hZ2UodHJhbnMubGlzdFtbMl1dKQpgYGAKVGhlc2UgcmVzdWx0cyBmb3IgdGhlIDUwMDAgZGF0YXNldCBmb3IgdGhlIHNhbWUgbWluc3VwIHZsdWUgb2YgMC4xIHRoZSB0b3RhbCBudW1iZXIgb2YgaXRlbXNldHMgaXMgbG93ZXIgdGhhbiB0aGUgMTAwMCBkYXRhc2V0LgoKIyMgMjAwMDAgZGF0YXNldCAjIwoKYGBge3J9CmZyZXFfaXRlbXNldF8yMGsgPC0gYXByaW9yaSh0cmFucy5saXN0W1szXV0sIHBhcmFtZXRlcj1saXN0KHN1cHBvcnQ9MC4xLCB0YXJnZXQ9ImZyZXF1ZW50IGl0ZW1zZXRzIikpCmluc3BlY3Qoc29ydChmcmVxX2l0ZW1zZXRfMjBrLCBkZWNyZWFzaW5nID0gVCwgYnk9ImNvdW50IikpCnJtKGZyZXFfaXRlbXNldF8yMGspCml0ZW1GcmVxdWVuY3lQbG90KHRyYW5zLmxpc3RbWzNdXSwgc3VwcG9ydCA9IDAuMSkKaW1hZ2UodHJhbnMubGlzdFtbM11dKQpgYGAKVGhlc2UgcmVzdWx0cyBmb3IgdGhlIDIwMDAwIGRhdGFzZXQgaXMgc2ltaWxhciB0byB0aGUgNTAwMCBkYXRhc2V0LgoKIyMgNzUwMDAgZGF0YXNldCAjIwoKYGBge3J9CmZyZXFfaXRlbXNldF83NWsgPC0gYXByaW9yaSh0cmFucy5saXN0W1s0XV0sIHBhcmFtZXRlcj1saXN0KHN1cHBvcnQ9MC4xLCB0YXJnZXQ9ImZyZXF1ZW50IGl0ZW1zZXRzIikpCmluc3BlY3Qoc29ydChmcmVxX2l0ZW1zZXRfNzVrLCBkZWNyZWFzaW5nID0gVCwgYnk9ImNvdW50IikpCnJtKGZyZXFfaXRlbXNldF83NWspCml0ZW1GcmVxdWVuY3lQbG90KHRyYW5zLmxpc3RbWzRdXSwgc3VwcG9ydCA9IDAuMSkKaW1hZ2UodHJhbnMubGlzdFtbNF1dKQpgYGAKClRoZXNlIHJlc3VsdHMgZm9yIHRoZSA3NTAwMCBkYXRhc2V0IGlzIG1vcmUgdGhhbiB0aGUgNTAwMCBhbmQgdGhlIDIwMDAwIGRhdGFzZXQgd2l0aHQgdGhlIGV4Y2VwdGlvbiBvZiBUdWlsZSBDb29raWUuCgojIyMjIE1pbnN1cCB2YWx1ZQoKVGhlcmUgaXMgYSBjaGFuZ2UgaW4gdGhlIGZyZXF1ZW50IGl0ZW1zIGZyb20gdGhlIDEwMDAgZGF0YXNldCBhbmQgaGlnaCBxdWFudGl0eSBvZiB0eXBlcyBvZiBDb2ZmZWUgZnJvbSB0aGUgNTAwMCBkYXRhc2V0LlRoZSBudW1iZXIgb2YgdGhlc2UgZnJlcXVlbnQgaXRlbXMgaXMgaW5jcmVhc2VkIGluIGVhY2ggZGF0YXNldC4gVGh1cyBjdXN0b21lcnMgY29udGludWUgdG8gYnV5IGNvZmZlZSB0aWxsIHRoZSA3NTAwMCBkYXRhdHNldCBhbmQgdGhlIHRoZSBUdWlsZSBDb29raWUgaXMgYW5vdGhlciBmcmVxdWVudCBpdGVtLgoKCi0gR29uZ29sYWlzIENvb2tpZQotIFRydWZmbGUgQ2FrZSAKLSBUdWlsZSBDb29raWUKClRvIHRoZSBtb3N0IGZyZXF1ZW50IGl0ZW1zOgoKLUNvZmZlZSBFY2xhaXIKLUhvdCBDb2ZmZWUKCiMjIyMgT3V0cHV0IGZvciBlYWNoIHJ1bGUuCgojIFdlIHRyeSB0byBjcmVhdGUgbmV3IHJ1bGVzIHVzaW5nIHRoZSBtaW5zdXAgdmFsdWUgdG8gMC4wMS4KCiMjIDEwMDAgREFUQVNFVCMjIAoKYGBge3J9CnJ1bGVzLjFrIDwtIGFwcmlvcmkodHJhbnMubGlzdFtbMV1dLCBwYXJhbWV0ZXIgPSBsaXN0KHN1cHBvcnQ9MC4wMSwgY29uZj0wLjUpKQpuZXcucnVsZXMuMWsgPC0gcnVsZXMuMWtbIWR1cGxpY2F0ZWQoZ2VuZXJhdGluZ0l0ZW1zZXRzKHJ1bGVzLjFrKSldCnN1bW1hcnkobmV3LnJ1bGVzLjFrKQpuZXcucnVsZXMuMWsuc29ydC5zdXBwb3J0IDwtIHNvcnQobmV3LnJ1bGVzLjFrLCBieT0ic3VwcG9ydCIpCm5ldy5ydWxlcy4xay5zb3J0LmxpZnQgPC0gc29ydChuZXcucnVsZXMuMWssIGJ5PSJsaWZ0IikKbmV3LnJ1bGVzLjFrLnNvcnQuY29uZmkgPC0gc29ydChuZXcucnVsZXMuMWssIGJ5PSJjb25maWRlbmNlIikKYGBgCgojIyA1MDAwIERBVEFTRVQjIyAKCmBgYHtyfQpydWxlcy41ayA8LSBhcHJpb3JpKHRyYW5zLmxpc3RbWzJdXSwgcGFyYW1ldGVyID0gbGlzdChzdXBwb3J0PTAuMDEsIGNvbmY9MC41KSkKbmV3LnJ1bGVzLjVrIDwtIHJ1bGVzLjVrWyFkdXBsaWNhdGVkKGdlbmVyYXRpbmdJdGVtc2V0cyhydWxlcy41aykpXQpzdW1tYXJ5KG5ldy5ydWxlcy41aykKbmV3LnJ1bGVzLjVrLnNvcnQuc3VwcG9ydCA8LSBzb3J0KG5ldy5ydWxlcy41aywgYnk9InN1cHBvcnQiKQpuZXcucnVsZXMuNWsuc29ydC5saWZ0IDwtIHNvcnQobmV3LnJ1bGVzLjVrLCBieT0ibGlmdCIpCm5ldy5ydWxlcy41ay5zb3J0LmNvbmZpIDwtIHNvcnQobmV3LnJ1bGVzLjVrLCBieT0iY29uZmlkZW5jZSIpCmBgYAoKIyMgMjAwMDAgREFUQVNFVCMjIAoKYGBge3J9CnJ1bGVzLjIwayA8LSBhcHJpb3JpKHRyYW5zLmxpc3RbWzNdXSwgcGFyYW1ldGVyID0gbGlzdChzdXBwb3J0PTAuMDEsIGNvbmY9MC41KSkKbmV3LnJ1bGVzLjIwayA8LSBydWxlcy4yMGtbIWR1cGxpY2F0ZWQoZ2VuZXJhdGluZ0l0ZW1zZXRzKHJ1bGVzLjIwaykpXQpzdW1tYXJ5KG5ldy5ydWxlcy4yMGspCm5ldy5ydWxlcy4yMGsuc29ydC5zdXBwb3J0IDwtIHNvcnQobmV3LnJ1bGVzLjIwaywgYnk9InN1cHBvcnQiKQpuZXcucnVsZXMuMjBrLnNvcnQubGlmdCA8LSBzb3J0KG5ldy5ydWxlcy4yMGssIGJ5PSJsaWZ0IikKbmV3LnJ1bGVzLjIway5zb3J0LmNvbmZpIDwtIHNvcnQobmV3LnJ1bGVzLjIwaywgYnk9ImNvbmZpZGVuY2UiKQpgYGAKCiMjIDc1MDAwIERBVEFTRVQjIyAKCmBgYHtyfQpydWxlcy43NWsgPC0gYXByaW9yaSh0cmFucy5saXN0W1s0XV0sIHBhcmFtZXRlciA9IGxpc3Qoc3VwcG9ydD0wLjAxLCBjb25mPTAuNSkpCiMgUmVtb3ZpbmcgdGhlIGR1cGxpY2F0ZWQgcnVsZXMKbmV3LnJ1bGVzLjc1ayA8LSBydWxlcy43NWtbIWR1cGxpY2F0ZWQoZ2VuZXJhdGluZ0l0ZW1zZXRzKHJ1bGVzLjc1aykpXQpzdW1tYXJ5KG5ldy5ydWxlcy43NWspCm5ldy5ydWxlcy43NWsuc29ydC5zdXBwb3J0IDwtIHNvcnQobmV3LnJ1bGVzLjc1aywgYnk9InN1cHBvcnQiKQpuZXcucnVsZXMuNzVrLnNvcnQubGlmdCA8LSBzb3J0KG5ldy5ydWxlcy43NWssIGJ5PSJsaWZ0IikKbmV3LnJ1bGVzLjc1ay5zb3J0LmNvbmZpIDwtIHNvcnQobmV3LnJ1bGVzLjc1aywgYnk9ImNvbmZpZGVuY2UiKQpgYGAKCiMjIyMgR3JhcGggZm9yIG5ldyBydWxlcy4KCmBgYHtyfQpwbG90KG5ldy5ydWxlcy4xaywgZW5naW5lPSJodG1sd2lkZ2V0IikKcGxvdChuZXcucnVsZXMuNWssIGVuZ2luZT0iaHRtbHdpZGdldCIpCnBsb3QobmV3LnJ1bGVzLjIwaywgZW5naW5lPSJodG1sd2lkZ2V0IikKcGxvdChuZXcucnVsZXMuNzVrLCBlbmdpbmU9Imh0bWx3aWRnZXQiKQpgYGAKCiMjIyBQYXJ0IDIuMS1jCgpSZXN1bHRzIG9ic2VydmVkIGFyZSBhcyBmb2xsb3dzIAoKVGhlIHRvdGFsIG51bWJlciBvZiBydWxlcyBoYXJkbHkgdmFyaWVzLiBUaGUgdG90YWwgbnVtYmVyIG9mIHJ1bGVzIGZvciBhIG1pbnN1cCBvZiAwLjAxIGFuZCBhIGNvbmZpZGVuY2Ugb2YgMC41IGlzOgoKKipDT05GSURFTkNFKioKCkluIHRoZSBjb25maWRlbmNlIHZhbHVlcyB0aGUgcnVsZXMgYXJlIGdyb3VwZWQgaW50byBzcGVjaWZpYyBjb25maWRlbmNlIHZhbHVlcyBhcyB0aGUgbnVtYmVyIG9mIHRyYW5zYWN0aW9ucyBpbmNyZWFzZXMuIAoKVGh1cyBmb3IgdGhlIDEwMDAgZGF0YXNldCB0aGUgcnVsZXMgYXJlIHdpZGVseSBzY2F0dGVyZWQgaW4gZGlmZmVyZW50IGNvbmZpZGVuY2UgdmFsdWVzLiBBbmQgd2hlbiB3ZSBsb29rIGF0IHRoZSA3NTAwMCBkYXRhc2V0LCB0aGUgcnVsZXMgd2l0aCBoaWdoZXN0IGxpZnQgdmFsdWUgYXJlIGdyb3VwZWQgaW4gY29uZmlkZW5jZSB2YWx1ZXMgYmV0d2VlbiAwLjkgYW5kIDEgYW5kIHRoZSBydWxlcyB3aXRoIGxvd2VzdCBsaWZ0IHZhbHVlIGluIGNvbmZpZGVuY2UgdmFsdWVzIGJldHdlZW4gMC41IGFuZCAwLjYuIFRoaXMgbWFrZXMgc2Vuc2UgYXMgbW9yZSB0aGUgdHJhbnNhY3Rpb25zIGFyZSBpbnB1dGVkIGluIGEgZGF0YXNldCwgdGhlIGNvbmZpZGVuY2Ugb2YgdGhlIGNvbmRpdGlvbiBvZiB0aGUgY29uc2VxdWVudCBpcyBjb21wbGllZCB3aXRoIHRoZSBhbnRlY2VkZW50IChvciBub3QpLCB0ZW5kcyB0byBhIHNwZWNpZmljIHZhbHVlLgoKKipTVVBQT1JUIExFVkVMKioKCkluIHRoZSBzdXBwb3J0IGxldmVsLCBhcyBtZW50aW9uZWQgYWJvdmUsIGFzIHRoZSBudW1iZXIgb2YgdHJhbnNhY3Rpb25zIGluY3JlYXNlcywgdGhlIHJ1bGVzIGFyZSBncm91cGVkIGludG8gc3BlY2lmaWMgY29uZmlkZW5jZSB2YWx1ZXMgYW5kIHRoZW4sIGl0IGlzIG9ic2VydmVkIHRoYXQgdGhlIHZhbHVlcyB3aXRoIGhpZ2hlc3QgbGlmdCBhcmUgZ3JvdXBlZCBpbiBzbWFsbCBzdXBwb3J0IHZhbHVlcyBmb3IgYSBoaWdoZXIgbnVtYmVyIG9mIHRyYW5zYWN0aW9ucy4gSW4gY2FzZSBmb3IgMTAwMCBkYXRhc2V0LCB0aGUgc3VwcG9ydCB2YWx1ZXMgYXJlIHdpZGVseSBzY2F0dGVyZWQuIEZvciA3NTAwMCBkYXRhc2V0IHRoZSBzdXBwb3J0IHZhbHVlcyBmb3IgdGhlIGhpZ2hlc3QgbGlmdCB2YWx1ZXMgYXJlIGdyb3VwZWQgYmV0d2VlbiAwLjAyIGFuZCAwLjAzIGFuZCB0aGUgc3VwcG9ydCB2YWx1ZXMgZm9yIGxvd2VzdCBsaWZ0IHZhbHVlcyBiZXR3ZWVuIDAuMDQgYW5kIDAuMDUuCgojIyMgUGFydCAyLjEtRAoKYGBge3J9Cm5ldy5ydWxlcy43NUsuc29ydC5zdXBwb3J0IDwtIHNvcnQobmV3LnJ1bGVzLjc1aywgYnk9InN1cHBvcnQiKQpgYGAKCiMjIyMgKEkpIE1vc3QgZnJlcXVlbnRseSBwdXJjaGFzZWQgaXRlbSBvciBpdGVtc2V0PwoKVGhlIG1vc3QgZnJlcXVlbnRseSBwdXJjaGFzZWQgaXRlbSBvciBpdGVtc2V0LiAKCmBgYHtyfQppbnNwZWN0KGhlYWQobmV3LnJ1bGVzLjc1Sy5zb3J0LnN1cHBvcnQsMSkpCmBgYApgYGB7cn0KY2F0KCJ7QXByaWNvdCBEYW5pc2h9ID0+IHtDaGVycnkgVGFydH0iKQpgYGAKCiMjIyMgTGVhc3QgZnJlcXVlbnRseSBwdXJjaGFzZWQgaXRlbSBvciBpdGVtc2V0PwoKVGhlIGxlYXN0IGZyZXF1ZW50bHkgcHVyY2hhc2VkIGl0ZW0gb3IgaXRlbSBzZXQuCgpgYGB7cn0KaW5zcGVjdCh0YWlsKG5ldy5ydWxlcy43NWsuc29ydC5zdXBwb3J0LDEpKQpgYGAKCmBgYHtyfQpjYXQoIntBcHBsZSBjcm9pc3NhbnQsIEFwcGxlIERhbmlzaCwgQ2hlcnJ5IFNvZGF9ID0+IHtBcHBsZSBUYXJ0fSIpCmBgYAoKCgoKCgoKCgoKCgoK